package jp.ddo.trismegistos.dao.impl;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import jp.ddo.trismegistos.dao.DaoManager;
import jp.ddo.trismegistos.exception.RsException;

/**
 * Daoの抽象クラス。
 * 
 * @author Nagai Satoshi
 */
abstract public class AbstractBaseDao {

	/** ログ */
	private static final Logger log = Logger.getLogger(AbstractBaseDao.class
			.getName());

	/** Daoマネージャ */
	private DaoManager manager;

	/**
	 * 引数に指定されたDaoマネージャを使用して新しいDaoのインスタンスを作成します。
	 * 
	 * @param manager
	 *            Daoマネージャ
	 */
	public AbstractBaseDao(final DaoManager manager) {
		this.manager = manager;
	}

	/**
	 * コネクションを取得します。
	 * 
	 * @param autoCommit
	 *            自動コミットするか
	 * @return コネクション
	 * @throws SQLException
	 *             コネクション生成時にエラーが発生した場合
	 */
	protected Connection getConnection(final boolean autoCommit)
			throws SQLException {
		final Connection conn = DriverManager.getConnection(
				manager.getJdbcUri(), manager.getDbUser(),
				manager.getDbPassword());
		conn.setAutoCommit(autoCommit);
		return conn;
	}

	/**
	 * Daoマネージャに保持しているSQL Mapから引数のSQL IDを使用してSQLを取得します。
	 * 
	 * @param sqlId
	 *            SQL ID(SQLファイル名から拡張子を除いたもの)
	 * @return SQL
	 */
	protected String getSql(final String sqlId) {
		return manager.getSql(sqlId);
	}

	/**
	 * 引数に渡されたPreparadStatementオブジェクトのidx番目のバインド変数にvalueをセットします。
	 * 
	 * @param ps
	 *            PreparedStatementオブジェクト
	 * @param idx
	 *            バインド先インデックス
	 * @param value
	 *            バインド値
	 * @throws SQLException
	 *             バインド値の設定中にエラーが発生した場合
	 */
	protected void setValue(final PreparedStatement ps, final Integer idx,
			final Object value) throws SQLException {
		if (value instanceof String) {
			ps.setString(idx, (String) value);
		} else if (value instanceof BigDecimal) {
			ps.setBigDecimal(idx, (BigDecimal) value);
		} else if (value instanceof Timestamp) {
			ps.setTimestamp(idx, (Timestamp) value);
		} else {
			// TODO
			log.debug(ps.toString() + idx.toString() + value.toString());
		}
	}

	/**
	 * ResultSetオブジェクトの1行をresultClassで指定されたオブジェクトに変換し、すべてをリストに格納して返します。
	 * 
	 * @param <T>
	 *            結果オブジェクトの型
	 * @param rs
	 *            ResultSetオブジェクト
	 * @param resultClass
	 *            結果オブジェクトのクラスオブジェクト
	 * @return 結果オブジェクトのリスト
	 * @throws SQLException
	 *             ResultSetオブジェクトからSQL実行結果を取得する際にエラーが発生した場合
	 * @throws RsException
	 */
	public static <T> List<T> processResult(final ResultSet rs,
			final Class<T> resultClass) throws SQLException, RsException {
		final List<T> resultList = new ArrayList<T>();
		try {
			while (rs.next()) {
				final T t = resultClass.newInstance();
				final Field[] fields = resultClass.getDeclaredFields();
				for (final Field field : fields) {
					field.setAccessible(true);
					final String type = field.getType().getName();
					if (String.class.getName().equals(type)) {
						field.set(t, rs.getString(field.getName()));
					} else if (BigDecimal.class.getName().equals(type)) {
						field.set(t, rs.getBigDecimal(field.getName()));
					} else if (Timestamp.class.getName().equals(type)) {
						field.set(t, rs.getTimestamp(field.getName()));
					} else {
						throw new RsException("Unsupported field type : "
								+ type);
					}
				}
				resultList.add(t);
			}
		} catch (final InstantiationException e) {
			throw new RsException(e);
		} catch (final IllegalAccessException e) {
			throw new RsException(e);
		}
		return resultList;
	}

	protected void rollBackConnection(final Connection conn) {
		try {
			conn.rollback();
		} catch (final SQLException ignore) {
			log.warn("Connection rollback error" + ignore.getMessage());
		}
	}

	protected void closeConnection(final Connection conn) {
		if (conn == null) {
			return;
		}
		try {
			conn.close();
		} catch (final SQLException ignore) {
			log.warn("Connection close error" + ignore.getMessage());
		}
	}

	protected void closeStatement(final Statement stat) {
		if (stat == null) {
			return;
		}
		try {
			stat.close();
		} catch (final SQLException ignore) {
			log.warn("Statement close error" + ignore.getMessage());
		}
	}

	protected void closeResultSet(final ResultSet rs) {
		if (rs == null) {
			return;
		}
		try {
			rs.close();
		} catch (final SQLException ignore) {
			log.warn("ResultSet close error" + ignore.getMessage());
		}
	}
}
